package com.hyjiacan.apps.audionly.data
import kotlin.test.assertNotNull

import com.hyjiacan.apps.audionly.data.model.AudioParam
import com.hyjiacan.apps.audionly.data.model.TaskInfo
import com.hyjiacan.apps.audionly.data.model.VideoFile
import com.hyjiacan.apps.audionly.data.repository.TaskRepository
import com.hyjiacan.apps.audionly.utils.ExoAudioExtractor
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.clearMocks
import io.mockk.mockk
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.setMain
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.util.Date
import java.util.UUID
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue

@ExperimentalCoroutinesApi
class TaskManagerTest {
    private lateinit var taskManager: TaskManager
    private val taskRepository: TaskRepository = mockk()
    private val audioExtractor: ExoAudioExtractor = mockk()
    private val testDispatcher = StandardTestDispatcher()

    @BeforeEach
    fun setUp() {
        Dispatchers.setMain(testDispatcher)
        taskManager = TaskManager(taskRepository, audioExtractor)
    }

    @AfterEach
    fun tearDown() {
        Dispatchers.resetMain()
        clearMocks(taskRepository, audioExtractor) // 清理mock状态，防止测试污染
    }

    @Test
    fun `createTask should create and insert task correctly`() = runTest {
        // Arrange
        val videoFile = createTestVideoFile()
        val audioParam = AudioParam()

        coEvery { taskRepository.insertTask(any()) } returns true

        // Act
        val result = taskManager.createTask(videoFile, audioParam)

        // Assert
        assertEquals(videoFile, result.videoFile)
        assertEquals(audioParam, result.audioParam)
        assertEquals(TaskInfo.Status.PENDING, result.status)
        assertNotNull(result.createTime)
        coVerify { taskRepository.insertTask(result) }
    }

    @Test
    fun `startTask should start task successfully when task is pending`() = runTest {
        // Arrange
        val taskId = UUID.randomUUID().toString()
        val taskInfo = createTestTaskInfo(taskId, TaskInfo.Status.PENDING)
        var progressUpdateCount = 0

        coEvery { taskRepository.getTaskById(taskId) } returns taskInfo
        coEvery { taskRepository.updateTask(any()) } returns true
        coEvery { audioExtractor.extractAudio(any(), any()) } returns true

        // Act
        val result = taskManager.startTask(taskId) { _, _ ->
            progressUpdateCount++
        }

        // 推进测试调度器执行所有挂起函数
        testDispatcher.scheduler.advanceUntilIdle()

        // Assert
        assertTrue(result)
        assertTrue(taskManager.isTaskActive(taskId))
        coVerify { taskRepository.getTaskById(taskId) }
        coVerify { taskRepository.updateTask(match { it.status == TaskInfo.Status.PROCESSING }) }
        // 验证进度回调被调用
        assertTrue(progressUpdateCount > 0)
    }

    @Test
    fun `startTask should return false when task not found`() = runTest {
        // Arrange
        val taskId = UUID.randomUUID().toString()
        coEvery { taskRepository.getTaskById(taskId) } returns null

        // Act
        val result = taskManager.startTask(taskId) { _, _ -> }

        // Assert
        assertFalse(result)
        coVerify { taskRepository.getTaskById(taskId) }
        coVerify(exactly = 0) { taskRepository.updateTask(any()) }
        coVerify(exactly = 0) { audioExtractor.extractAudio(any(), any()) }
    }

    @Test
    fun `startTask should return false when task is not pending`() = runTest {
        // Arrange
        val taskId = UUID.randomUUID().toString()
        val taskInfo = createTestTaskInfo(taskId, TaskInfo.Status.COMPLETED)

        coEvery { taskRepository.getTaskById(taskId) } returns taskInfo

        // Act
        val result = taskManager.startTask(taskId) { _, _ -> }

        // Assert
        assertFalse(result)
        coVerify { taskRepository.getTaskById(taskId) }
        coVerify(exactly = 0) { taskRepository.updateTask(any()) }
        coVerify(exactly = 0) { audioExtractor.extractAudio(any(), any()) }
    }

    @Test
    fun `cancelTask should cancel task successfully when task is processing`() = runTest {
        // Arrange
        val taskId = UUID.randomUUID().toString()
        val taskInfo = createTestTaskInfo(taskId, TaskInfo.Status.PENDING)

        coEvery { taskRepository.getTaskById(taskId) } returns taskInfo
        coEvery { taskRepository.updateTask(any()) } returns true
        coEvery { audioExtractor.extractAudio(any(), any()) } returns true

        // Start task first
        taskManager.startTask(taskId) { _, _ -> }
        testDispatcher.scheduler.advanceUntilIdle()
        assertTrue(taskManager.isTaskActive(taskId))

        // Act
        val result = taskManager.cancelTask(taskId)

        // Assert
        assertTrue(result)
        assertFalse(taskManager.isTaskActive(taskId))
        coVerify { taskRepository.getTaskById(taskId) }
        coVerify { taskRepository.updateTask(match { it.status == TaskInfo.Status.FAILED }) }
    }

    @Test
    fun `cancelTask should return false when task not found`() = runTest {
        // Arrange
        val taskId = UUID.randomUUID().toString()
        coEvery { taskRepository.getTaskById(taskId) } returns null

        // Act
        val result = taskManager.cancelTask(taskId)

        // Assert
        assertFalse(result)
        coVerify { taskRepository.getTaskById(taskId) }
        coVerify(exactly = 0) { taskRepository.updateTask(any()) }
    }

    @Test
    fun `cancelTask should return false when task is not processing`() = runTest {
        // Arrange
        val taskId = UUID.randomUUID().toString()
        val taskInfo = createTestTaskInfo(taskId, TaskInfo.Status.PENDING)

        coEvery { taskRepository.getTaskById(taskId) } returns taskInfo

        // Act
        val result = taskManager.cancelTask(taskId)

        // Assert
        assertFalse(result)
        coVerify { taskRepository.getTaskById(taskId) }
        coVerify(exactly = 0) { taskRepository.updateTask(any()) }
    }

    @Test
    fun `getAllTasks should return all tasks from repository`() = runTest {
        // Arrange
        val tasks = listOf(
            createTestTaskInfo(UUID.randomUUID().toString()),
            createTestTaskInfo(UUID.randomUUID().toString())
        )
        coEvery { taskRepository.getAllTasks() } returns tasks

        // Act
        val result = taskManager.getAllTasks()

        // Assert
        assertEquals(tasks, result)
        coVerify { taskRepository.getAllTasks() }
    }

    @Test
    fun `getTasksByStatus should return tasks with specified status`() = runTest {
        // Arrange
        val status = TaskInfo.Status.COMPLETED
        val tasks = listOf(
            createTestTaskInfo(UUID.randomUUID().toString(), status),
            createTestTaskInfo(UUID.randomUUID().toString(), status)
        )
        coEvery { taskRepository.getTasksByStatus(status) } returns tasks

        // Act
        val result = taskManager.getTasksByStatus(status)

        // Assert
        assertEquals(tasks, result)
        coVerify { taskRepository.getTasksByStatus(status) }
    }

    private fun createTestVideoFile(): VideoFile {
        return VideoFile(
            id = UUID.randomUUID().toString(),
            name = "test_video.mp4",
            path = "/storage/emulated/0/Download/test_video.mp4",
            duration = 60000,
            size = 10485760,
            format = "mp4"
        )
    }

    private fun createTestTaskInfo(
        taskId: String = UUID.randomUUID().toString(),
        status: TaskInfo.Status = TaskInfo.Status.PENDING
    ): TaskInfo {
        return TaskInfo(
            id = taskId,
            videoFile = createTestVideoFile(),
            audioParam = AudioParam(),
            status = status,
            createTime = Date(),
            startTime = if (status != TaskInfo.Status.PENDING) Date() else null,
            endTime = if (status == TaskInfo.Status.COMPLETED || status == TaskInfo.Status.FAILED) Date() else null,
            outputPath = if (status == TaskInfo.Status.COMPLETED) "/storage/emulated/0/Download/test_audio.mp3" else null
        )
    }
}
